<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Go 应用代码解析 - HTTP 请求处理</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <header>
        <h1>5. 处理具体请求 (Handler 函数)</h1>
        <nav>
            <a href="index.html">概述</a> |
            <a href="01_setup_dependencies.html">1. 依赖与全局变量</a> |
            <a href="02_opentelemetry_init.html">2. OpenTelemetry 初始化</a> |
            <a href="03_database_redis_init.html">3. 数据库与 Redis 初始化</a> |
            <a href="04_web_server_routing.html">4. Web 服务器与路由</a> |
            <a href="05_request_handlers.html" class="active">5. HTTP 请求处理</a> |
            <a href="06_business_logic.html">6. 核心业务逻辑</a> |
            <a href="07_main_startup_shutdown.html">7. 程序启动与关闭</a>
        </nav>
    </header>

    <main>
        <p>Handler 函数是 Gin 路由最终指向的地方，它们负责承接上游（Gin 引擎和中间件）传来的请求，执行一些初步处理（比如解析参数），然后调用下一层（业务逻辑 Service 层）去完成核心任务，最后将结果返回给客户端。</p>
        <p>Handler 层面也应该创建自己的 OTel Span，作为 `otelgin` 中间件创建的 "gin-server" Span 的子 Span，这样能更清晰地看到请求处理的具体步骤耗时。</p>

        <section id="get-user-handler">
            <h2>处理“查用户”请求：`getUserHandler`</h2>
            <p>这个函数对应 `GET /users/:id` 路由，负责根据 URL 中提供的用户 ID 来查询用户信息。</p>

            <pre><code class="language-go">// getUserHandler 处理 GET /users/:id 请求
func getUserHandler(c *gin.Context) {
	// 1. 从 Gin 上下文 (Context) 中获取请求的上下文 (包含 OTel 信息)
	// 这是 Go 处理并发请求的标准做法，后续所有调用都应传递这个 ctx
	ctx := c.Request.Context()

	// 2. 获取 URL 路径参数 ":id" 的值
	userID := c.Param("id")
	if userID == "" {
		// 如果 ID 为空，返回 400 Bad Request 错误
		c.JSON(http.StatusBadRequest, gin.H{"error": "用户 ID 不能为空"})
		return // 结束处理
	}

	// 3. 开始一个新的 OTel Span (作为 "gin-server" Span 的子 Span)
	var handlerSpan trace.Span
	// 使用全局 tracer 创建 Span，命名为 "Handler: GetUserByID"
	// 父 Span 信息会自动从 ctx 中提取
	ctx, handlerSpan = tracer.Start(ctx, "Handler: GetUserByID",
		// 添加一些关于这个 Span 的属性 (Attributes)
		trace.WithAttributes(
			attribute.String("app.user.id", userID), // 记录要查询的用户 ID
			// 使用 semconv 标准属性名记录 HTTP 方法和请求路径模板
			semconv.HTTPRequestMethodKey.String(c.Request.Method),
			semconv.URLPathKey.String(c.FullPath()),
		),
		// 设置 Span 的种类为 Internal，表示这是服务内部的一个处理步骤
		trace.WithSpanKind(trace.SpanKindInternal),
	)
	// 使用 defer 确保在函数退出时，这个 Span 会被正确结束 (End)
	defer handlerSpan.End()

	// 4. 在 Span 中添加一个事件 (Event)
	handlerSpan.AddEvent("开始处理 GetUser 请求")

	// 5. 调用业务逻辑层 (Service Layer) 去真正查找用户
	// 把包含 OTel Span 的 ctx 传递下去是关键！
	user, err := findUserByID(ctx, userID) // 这个函数我们将在下一节分析

	// 6. 处理业务逻辑层返回的结果或错误
	if err != nil {
		// 如果有错误发生：
		// a) 在 Span 上记录这个错误信息
		handlerSpan.RecordError(err, trace.WithAttributes(attribute.String("app.error.type", "UserLookupFailed")))
		// b) 判断错误类型并设置 Span 的状态为 Error
		if errors.Is(err, gorm.ErrRecordNotFound) {
			// 如果是 GORM 的 "记录未找到" 错误
			handlerSpan.SetStatus(codes.Error, "User not found") // 设置 OTel 状态
			c.JSON(http.StatusNotFound, gin.H{"error": "用户未找到"}) // 返回 404 Not Found
		} else {
			// 其他类型的错误 (比如数据库连接失败)
			handlerSpan.SetStatus(codes.Error, "Internal error finding user") // 设置 OTel 状态
			c.JSON(http.StatusInternalServerError, gin.H{"error": "查找用户时发生内部错误: " + err.Error()}) // 返回 500 Internal Server Error
		}
		return // 结束处理
	}

	// 7. 如果成功找到用户：
	// a) 设置 Span 状态为 OK
	handlerSpan.SetStatus(codes.Ok, "User found")
	// b) 添加一个成功事件，并附带额外信息 (用户名)
	handlerSpan.AddEvent("成功获取用户数据", trace.WithAttributes(attribute.String("app.user.name", user.Name)))
	// c) 给 Span 添加一个属性，记录成功获取到的用户 ID (有时可能与请求 ID 不同或做验证)
	handlerSpan.SetAttributes(attribute.String("app.user.retrieved_id", user.ID))
	// d) 将用户信息以 JSON 格式返回给客户端，状态码 200 OK
	c.JSON(http.StatusOK, user)
}</code></pre>
            <p><strong>核心流程：</strong></p>
            <ol>
                <li><strong>获取上下文和参数:</strong> 从 Gin 的 `c *gin.Context` 中拿到带有 OTel 信息的 `ctx` 和 URL 中的 `userID`。</li>
                <li><strong>启动 OTel Span:</strong> 创建一个名为 "Handler: GetUserByID" 的 Span，记录请求的关键信息（用户 ID、HTTP 方法、路径），并将它设为 `ctx` 中当前活动的 Span。使用 `defer handlerSpan.End()` 确保 Span 被关闭。</li>
                <li><strong>添加事件:</strong> 在 Span 时间线上标记一个点，说明处理开始了。</li>
                <li><strong>调用业务逻辑:</strong> 把带有新 Span 的 `ctx` 传递给 `findUserByID` 函数（下一层），让它去执行实际的查找操作。这是将 OTel 追踪链传递下去的关键一步。</li>
                <li><strong>处理结果:</strong>
                    <ul>
                        <li><strong>失败时:</strong> 在 Span 上记录错误详情 (`RecordError`)，设置 Span 的状态为错误 (`SetStatus(codes.Error, ...)`), 并根据错误类型（找不到用户 vs. 内部错误）返回不同的 HTTP 状态码和错误消息给客户端 (404 vs. 500)。</li>
                        <li><strong>成功时:</strong> 设置 Span 状态为成功 (`SetStatus(codes.Ok, ...)`), 添加成功事件和相关属性, 然后将查询到的 `user` 对象序列化为 JSON，作为 HTTP 响应体返回给客户端 (200 OK)。</li>
                    </ul>
                </li>
            </ol>
        </section>

        <section id="create-user-handler">
            <h2>处理“创建用户”请求：`createUserHandler`</h2>
            <p>这个函数对应 `POST /users` 路由，负责接收客户端提交的用户信息（JSON 格式），并将其保存到系统中。</p>

            <pre><code class="language-go">// createUserHandler 处理 POST /users 请求
func createUserHandler(c *gin.Context) {
	// 1. 获取请求上下文
	ctx := c.Request.Context()

	// 2. 解析请求体中的 JSON 数据到 User 结构体
	var newUser User // 创建一个空的 User 结构体变量
	// c.ShouldBindJSON(&newUser) 会尝试读取请求 Body 中的 JSON 数据，
	// 并根据 JSON 的字段名填充到 newUser 结构体中对应的字段。
	if err := c.ShouldBindJSON(&newUser); err != nil {
		// 如果 JSON 解析失败或格式不对，返回 400 Bad Request
		c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求数据: " + err.Error()})
		return // 结束处理
	}

	// 3. 开始一个新的 OTel Span
	var handlerSpan trace.Span
	ctx, handlerSpan = tracer.Start(ctx, "Handler: CreateUser",
		trace.WithAttributes(
			// 记录一些请求的元信息
			attribute.Bool("app.user.email_provided", newUser.Email != ""), // 记录是否提供了 Email
			semconv.HTTPRequestMethodKey.String(c.Request.Method),
			semconv.URLPathKey.String(c.FullPath()),
		),
		trace.WithSpanKind(trace.SpanKindInternal),
	)
	defer handlerSpan.End()

	// 4. 添加事件并处理用户 ID (如果客户端没提供，就生成一个)
	handlerSpan.AddEvent("开始处理 CreateUser 请求")
	if newUser.ID == "" {
		// 如果请求的 JSON 中没有包含 ID 字段
		newUser.ID = uuid.NewString() // 使用 google/uuid 库生成一个新的唯一 ID
		// 在 Span 上记录 ID 是自动生成的，并记录生成的 ID 值
		handlerSpan.SetAttributes(
			attribute.String("app.user.id_generated", "true"),
			attribute.String("app.user.id", newUser.ID),
		)
		log.Printf("为新用户生成 ID: %s", newUser.ID) // 同时打印日志
	} else {
		// 如果客户端提供了 ID
		handlerSpan.SetAttributes(
			attribute.String("app.user.id_generated", "false"), // 记录 ID 是由客户端提供的
			attribute.String("app.user.id", newUser.ID),
		)
	}

	// 5. 基本的业务逻辑校验 (用户名和邮箱不能为空)
	if newUser.Name == "" || newUser.Email == "" {
		err := errors.New("用户名和邮箱不能为空") // 创建一个简单的错误
		handlerSpan.RecordError(err)            // 在 Span 上记录错误
		handlerSpan.SetStatus(codes.Error, "Invalid input data") // 设置 Span 状态为错误
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) // 返回 400 Bad Request
		return // 结束处理
	}

	// 6. 调用业务逻辑层 (Service Layer) 去真正保存用户
	// 把包含 OTel Span 的 ctx 和准备好的 newUser 对象传递下去
	createdUser, err := saveUser(ctx, &newUser) // 下一节分析 saveUser

	// 7. 处理业务逻辑层返回的结果或错误
	if err != nil {
		// 如果保存失败：
		// a) 在 Span 上记录错误
		handlerSpan.RecordError(err, trace.WithAttributes(attribute.String("app.error.type", "UserCreationFailed")))
		// b) 设置 Span 状态为错误
		handlerSpan.SetStatus(codes.Error, "Failed to create user")
		// c) 返回 500 Internal Server Error
		c.JSON(http.StatusInternalServerError, gin.H{"error": "创建用户失败: " + err.Error()})
		return // 结束处理
	}

	// 8. 如果成功创建用户：
	// a) 设置 Span 状态为 OK
	handlerSpan.SetStatus(codes.Ok, "User created")
	// b) 在 Span 上添加一个属性，记录成功创建的用户 ID
	handlerSpan.SetAttributes(attribute.String("app.user.created_id", createdUser.ID))
	// c) 添加一个成功事件
	handlerSpan.AddEvent("用户创建成功")
	// d) 将创建成功的用户信息 (包含数据库生成的 CreatedAt/UpdatedAt) 以 JSON 返回
	//    状态码用 201 Created，表示资源成功创建
	c.JSON(http.StatusCreated, createdUser)
}
</code></pre>
            <p><strong>核心流程：</strong></p>
            <ol>
                <li><strong>获取上下文:</strong> 拿到 `ctx`。</li>
                <li><strong>解析请求体:</strong> 使用 `c.ShouldBindJSON()` 把请求 Body 中的 JSON 数据绑定到 `newUser` 变量上。如果失败则返回 400 错误。</li>
                <li><strong>启动 OTel Span:</strong> 创建 "Handler: CreateUser" Span，记录请求元信息，设置当前活动 Span，并确保结束。</li>
                <li><strong>处理/生成 ID:</strong> 检查客户端是否提供了用户 ID，如果没有，就调用 `uuid.NewString()` 生成一个新的，并在 Span 上做记录。</li>
                <li><strong>输入校验:</strong> 做一些基本的检查，比如用户名和邮箱不能为空。如果校验失败，记录 OTel 错误，返回 400 错误。</li>
                <li><strong>调用业务逻辑:</strong> 将带有 Span 的 `ctx` 和包含完整信息的 `newUser` 对象传递给 `saveUser` 函数（下一层）执行保存操作。</li>
                <li><strong>处理结果:</strong>
                    <ul>
                        <li><strong>失败时:</strong> 记录 OTel 错误，设置 Span 状态，返回 500 错误给客户端。</li>
                        <li><strong>成功时:</strong> 设置 Span 状态为 OK，添加成功事件和属性，然后将 `saveUser` 返回的（可能包含数据库生成的时间戳等）`createdUser` 对象序列化为 JSON，以 `201 Created` 状态码返回给客户端。</li>
                    </ul>
                </li>
            </ol>
             <p>这两个 Handler 函数展示了典型的 Gin Handler 模式：解析输入 -> 启动 OTel Span -> （可选的输入校验/准备） -> 调用业务逻辑 -> 处理业务逻辑结果并返回 HTTP 响应。</p>
        </section>

        <section id="navigation">
            <h2>下一步</h2>
            <p>Handler 函数只是“传达指令”的角色，真正干脏活累活（操作数据库、缓存）的是业务逻辑层 (Service Layer)。</p>
            <p>➡️ <a href="06_business_logic.html">点击这里，深入核心业务逻辑</a></p>
        </section>
    </main>

    <footer>
        <p>Go 应用代码解析</p>
    </footer>
</body>
</html>